package hos.http.retrofit;

import static hos.http.retrofit.TypeUtil.methodError;
import static hos.http.retrofit.TypeUtil.parameterError;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import hos.http.Http;
import hos.http.HttpParam;
import hos.http.HttpUtils;
import hos.http.anotation.BaseUrl;
import hos.http.anotation.BaseUrlEnd;
import hos.http.anotation.Data;
import hos.http.anotation.Download;
import hos.http.anotation.ExceptionInterceptor;
import hos.http.anotation.File;
import hos.http.anotation.GET;
import hos.http.anotation.HTTP;
import hos.http.anotation.Header;
import hos.http.anotation.HeaderMap;
import hos.http.anotation.POST;
import hos.http.anotation.Param;
import hos.http.anotation.ParamMap;
import hos.http.anotation.Query;
import hos.http.anotation.QueryMap;
import hos.http.anotation.RequestInterceptor;
import hos.http.anotation.ResponseInterceptor;
import hos.http.anotation.Soap;
import hos.http.anotation.SoapDotNet;
import hos.http.anotation.SoapHeader;
import hos.http.anotation.SoapHeaderMap;
import hos.http.anotation.SoapNameSpace;
import hos.http.anotation.SoapVersion;
import hos.http.anotation.Url;


/**
 * <p>Title: RetrofitRequest </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2024-04-12 20:55
 */
public final class RetrofitRequestFactory {

    private final Class<?> service;
    private final Method method;

    private final String baseUrl;
    private final String baseUrlEnd;
    private final boolean baseUrlUse;

    private final int soapVersion;
    private final boolean soapDotNet;
    private final String soapNameSpace;
    private final String soapMethodName;
    private final String url;
    private final String httpMethod;
    private final Type genericReturnType;
    private final Map<String, String> headers;
    private final Map<String, String> soapHeaders;
    private final String contentType;
    private final List<HttpParam> urlParam;
    private final List<RetrofitParamAnnotation> methodParam;
    private final boolean hasBody;
    private final boolean isFormEncoded;
    private final boolean isMultipart;
    private final Type paresType;

    RetrofitRequestFactory(Builder builder) {
        this.service = builder.service;
        this.method = builder.method;
        this.baseUrl = builder.baseUrl;
        this.baseUrlEnd = builder.baseUrlEnd;
        this.baseUrlUse = builder.baseUrlUse;
        this.soapVersion = builder.soapVersion;
        this.soapNameSpace = builder.soapNameSpace;
        this.url = builder.url;
        this.httpMethod = builder.httpMethod;
        this.genericReturnType = builder.genericReturnType;
        this.paresType = builder.paresType;
        this.headers = builder.headers;
        this.soapHeaders = builder.soapHeaders;
        this.soapMethodName = builder.soapMethodName;
        this.contentType = builder.contentType;
        this.urlParam = builder.urlParam;
        this.methodParam = builder.methodParam;
        this.soapDotNet = builder.soapDotNet;
        this.hasBody = builder.hasBody;
        this.isFormEncoded = builder.isFormEncoded;
        this.isMultipart = builder.isMultipart;
    }

    //<editor-fold desc="Get">

    public Class<?> getService() {
        return service;
    }

    public Method getMethod() {
        return method;
    }

    public String getBaseUrl() {
        return baseUrl;
    }

    public String getBaseUrlEnd() {
        return baseUrlEnd;
    }

    public boolean isBaseUrlUse() {
        return baseUrlUse;
    }

    public int getSoapVersion() {
        return soapVersion;
    }

    public boolean isSoapDotNet() {
        return soapDotNet;
    }

    public String getSoapNameSpace() {
        return soapNameSpace;
    }

    public String getSoapMethodName() {
        return soapMethodName;
    }

    public String getUrl() {
        return url;
    }

    public String getHttpMethod() {
        return httpMethod;
    }

    public Type getGenericReturnType() {
        return genericReturnType;
    }

    public Type getParesType() {
        return paresType;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public Map<String, String> getSoapHeaders() {
        return soapHeaders;
    }


    public String getContentType() {
        return contentType;
    }

    public List<HttpParam> getUrlParam() {
        return urlParam;
    }

    public List<RetrofitParamAnnotation> getMethodParam() {
        return methodParam;
    }

    public boolean isHasBody() {
        return hasBody;
    }

    public boolean isFormEncoded() {
        return isFormEncoded;
    }

    public boolean isMultipart() {
        return isMultipart;
    }
    //</editor-fold>

    public static RetrofitRequestFactory parseAnnotations(Class<?> service, Method method) {
        return new Builder(service, method).build();
    }

    private static class Builder {

        private static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
//        private static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
//        private static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);

        final Class<?> service;
        final Method method;

        String baseUrl;
        String baseUrlEnd;
        boolean baseUrlUse;

        int soapVersion;
        boolean soapDotNet = true;
        String soapNameSpace;
        String url;
        String httpMethod;
        Type genericReturnType;
        Type paresType;
        final Map<String, String> headers = new LinkedHashMap<>();
        final Map<String, String> soapHeaders = new LinkedHashMap<>();
        String contentType;
        List<HttpParam> urlParam;
        List<RetrofitParamAnnotation> methodParam;
        boolean hasBody;
        boolean isFormEncoded;
        boolean isMultipart;

        private String soapMethodName;

        public Builder(Class<?> service, Method method) {
            this.service = service;
            this.method = method;
        }

        public RetrofitRequestFactory build() {
//            baseUrl = retrofit.getBaseUrl();
//            soapNameSpace = retrofit.getNameSpace();
            // 返回类型包含泛型
            genericReturnType = method.getGenericReturnType();
            // 类注解
            Annotation[] serviceAnnotations = service.getAnnotations();
            for (Annotation serviceAnnotation : serviceAnnotations) {
                parseClassAnnotation(serviceAnnotation);
            }
            // 方法注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                parseMethodAnnotation(annotation);
            }
            if (methodParam == null) {
                methodParam = new LinkedList<>();
            }
            methodParam.clear();
            // 参数注解
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            // 参数类型
            Type[] genericParameterTypes = method.getGenericParameterTypes();
            int parameterAnnotationLength = parameterAnnotations.length;
            if (parameterAnnotationLength != 0) {
                for (int i = 0; i < parameterAnnotationLength; i++) {
                    Annotation[] parameterAnnotation = parameterAnnotations[i];
                    Type type = genericParameterTypes[i];
                    for (Annotation annotation : parameterAnnotation) {
                        parseParameterAnnotation(type, parameterAnnotation, annotation, i);
                    }
                }
            }
            return new RetrofitRequestFactory(this);
        }


        /**
         * 类注解解析
         *
         * @param serviceAnnotation 类注解
         */
        private void parseClassAnnotation(Annotation serviceAnnotation) {
            if (serviceAnnotation instanceof BaseUrl) {
                if (Http.config().isDebug()) {
                    baseUrl = ((BaseUrl) serviceAnnotation).defaultValue();
                } else {
                    baseUrl = ((BaseUrl) serviceAnnotation).value();
                }
                baseUrlUse = ((BaseUrl) serviceAnnotation).isUse();
            }
            if (serviceAnnotation instanceof BaseUrlEnd) {
                baseUrlEnd = ((BaseUrlEnd) serviceAnnotation).value();
            }
            if (serviceAnnotation instanceof SoapVersion) {
                soapVersion = ((SoapVersion) serviceAnnotation).value();
            }
            if (serviceAnnotation instanceof SoapDotNet) {
                soapDotNet = ((SoapDotNet) serviceAnnotation).value();
            }
            if (serviceAnnotation instanceof SoapNameSpace) {
                soapNameSpace = ((SoapNameSpace) serviceAnnotation).value();
            }
            if (serviceAnnotation instanceof hos.http.anotation.Headers) {
                hos.http.anotation.Headers headers = (hos.http.anotation.Headers) serviceAnnotation;
                String[] headersToParse = headers.value();
                if (headersToParse.length == 0) {
                    throw methodError(method, "@BaseHeaders annotation is empty.");
                }
                this.headers.putAll(parseHeaders(headersToParse));
            } else if (serviceAnnotation instanceof hos.http.anotation.SoapHeaders) {
                hos.http.anotation.SoapHeaders headers = (hos.http.anotation.SoapHeaders) serviceAnnotation;
                String[] headersToParse = headers.value();
                if (headersToParse.length == 0) {
                    throw methodError(method, "@SoapBaseHeaders annotation is empty.");
                }
                this.soapHeaders.putAll(parseHeaders(headersToParse));
            }
        }

        /**
         * 方法注解解析
         *
         * @param annotation 方法注解
         */
        private void parseMethodAnnotation(Annotation annotation) {
            if (annotation instanceof GET) {
                parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
            } else if (annotation instanceof POST) {
                parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
                isMultipart = ((POST) annotation).isMultipart();
                if (isMultipart) {
                    isFormEncoded = ((POST) annotation).isFormUrlEncoded();
                    if (isFormEncoded) {
                        throw methodError(method, "Only one encoding annotation is allowed.");
                    }
                }
            } else if (annotation instanceof Download) {
                parseHttpMethodAndPath("DOWNLOAD", ((Download) annotation).value(), true);
            } else if (annotation instanceof HTTP) {
                HTTP http = (HTTP) annotation;
                parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
            } else if (annotation instanceof Soap) {
                soapMethodName = ((Soap) annotation).methodName();
                parseHttpMethodAndPath("SOAP", ((Soap) annotation).value(), true);

            } else if (annotation instanceof hos.http.anotation.Headers) {
                hos.http.anotation.Headers headers = (hos.http.anotation.Headers) annotation;
                String[] headersToParse = headers.value();
                if (headersToParse.length == 0) {
                    throw methodError(method, "@Headers annotation is empty.");
                }
                this.headers.putAll(parseHeaders(headersToParse));
            } else if (annotation instanceof hos.http.anotation.SoapHeaders) {
                hos.http.anotation.SoapHeaders headers = (hos.http.anotation.SoapHeaders) annotation;
                String[] headersToParse = headers.value();
                if (headersToParse.length == 0) {
                    throw methodError(method, "@SoapHeaders annotation is empty.");
                }
                this.soapHeaders.putAll(parseHeaders(headersToParse));
            } else if (annotation instanceof hos.http.anotation.Type) {
                Class<?>[] classes = ((hos.http.anotation.Type) annotation).value();
                paresType = TypeUtil.typeOfArray(classes);
            }
            if (annotation instanceof BaseUrl) {
                if (Http.config().isDebug()) {
                    baseUrl = ((BaseUrl) annotation).defaultValue();
                } else {
                    baseUrl = ((BaseUrl) annotation).value();
                }
                baseUrlUse = ((BaseUrl) annotation).isUse();
            }
            if (annotation instanceof BaseUrlEnd) {
                baseUrlEnd = ((BaseUrlEnd) annotation).value();
            }
        }

        /**
         * 解析参数
         *
         * @param type                参数类型
         * @param parameterAnnotation 参数注解
         * @param annotation          参数单个注解
         * @param index               下标
         */
        private void parseParameterAnnotation(Type type, Annotation[] parameterAnnotation, Annotation annotation, int index) {
            if (annotation instanceof BaseUrl) {
                baseUrl = ((BaseUrl) annotation).value();
                if (Http.config().isDebug()) {
                    baseUrl = ((BaseUrl) annotation).defaultValue();
                }
                baseUrlUse = ((BaseUrl) annotation).isUse();
            }
            if (annotation instanceof BaseUrlEnd) {
                baseUrlEnd = ((BaseUrlEnd) annotation).value();
            }
            if (annotation instanceof Url) {
                if (type != String.class
                        && type != URI.class
                        && (!(type instanceof Class) || !"android.net.Uri".equals(((Class<?>) type).getName()))) {
                    throw parameterError(
                            method,
                            index,
                            "@Url must be String, java.net.URI, or android.net.Uri type.");
                }
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof hos.http.anotation.Tag) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof SoapHeaderMap) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof SoapHeader) {
                methodParam.add(new RetrofitParamAnnotation(annotation, ((SoapHeader) annotation).value(), index, type, false));
            }
            if (annotation instanceof QueryMap) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, ((QueryMap) annotation).encoded()));
            }
            if (annotation instanceof Query) {
                methodParam.add(new RetrofitParamAnnotation(annotation, ((Query) annotation).value(), index, type, ((Query) annotation).encoded()));
            }
            if (annotation instanceof ParamMap) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, ((ParamMap) annotation).encoded()));
            }
            if (annotation instanceof Param) {
                methodParam.add(new RetrofitParamAnnotation(annotation, ((Param) annotation).value(), index, type, ((Param) annotation).encoded()));
            }
            if (annotation instanceof Data) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof File) {
                methodParam.add(new RetrofitParamAnnotation(annotation, ((File) annotation).value(), index, type, false));
            }
            if (annotation instanceof HeaderMap) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof Header) {
                methodParam.add(new RetrofitParamAnnotation(annotation, ((Header) annotation).value(), index, type, false));
            }
            if (annotation instanceof ResponseInterceptor) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof RequestInterceptor) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
            if (annotation instanceof ExceptionInterceptor) {
                methodParam.add(new RetrofitParamAnnotation(annotation, "", index, type, false));
            }
        }

        private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
            if (this.httpMethod != null) {
                throw methodError(
                        method,
                        "Only one HTTP method is allowed. Found: %s and %s.",
                        this.httpMethod,
                        httpMethod);
            }
            this.httpMethod = httpMethod;
            this.hasBody = hasBody;

            if (value.isEmpty()) {
                return;
            }
            // 解析访问地址
            url = HttpUtils.parseUrl(value);
            // 解析访问地址中的参数
            List<HttpParam> urlParam = HttpUtils.parseUrlHttpParam(value);
            if (urlParam != null) {
                if (this.urlParam == null) {
                    this.urlParam = new LinkedList<>();
                }
                this.urlParam.clear();
                this.urlParam.addAll(urlParam);
            }
        }

        private Map<String, String> parseHeaders(String[] headers) {
            Map<String, String> headerMap = new LinkedHashMap<>();
            for (String header : headers) {
                int colon = header.indexOf(':');
                if (colon == -1 || colon == 0 || colon == header.length() - 1) {
                    throw methodError(
                            method, "@Headers value must be in the form \"Name: Value\". Found: \"%s\"", header);
                }
                String headerName = header.substring(0, colon);
                String headerValue = header.substring(colon + 1).trim();
                if ("Content-Type".equalsIgnoreCase(headerName)) {
                    contentType = headerValue;
                } else {
                    headerMap.put(headerName, headerValue);
                }
            }
            return headerMap;
        }
    }
}
